import fs from 'fs-extra';

import { logger } from '$common';
import { RunningState } from '../state/runningState';
import { reportService } from './reportService';
import { SimulationJob } from '../simulationJob';
import { QueueExceptionEnum } from '$domain/service/queue';
import { SimulationJobManager } from '../simulationJobManager';
import { SimulationQueueManager } from '../simulationQueueManager';
import { JobResultNotification, JobStatus, SimProcess, SimulationRequest } from '../type';
import { buildJobRunner, SimulationJobRunner, jobRunnerCache } from '../simulationJobRunner';

class SimulationJobService {

  private jobRepo = SimulationJobManager.Instance;

  private _buildSimulationJob(request: SimulationRequest): SimulationJob {
    const jobId = request.id;
    const product = request.product;
    const definition = request.definition;
    const extra = request.context;
    return new SimulationJob(jobId, product, definition, extra);
  }

  private _setTimeoutInterrupt(jobId: string, timeoutMinutes: number) {
    const max = 2147483647;
    const ms = timeoutMinutes * 60 * 1000;
  
    return setTimeout(() => {
      logger.debug(`🟡 Timeout(${timeoutMinutes} minutes), job(jobId=${jobId})`);
      const executionContext = jobRunnerCache[jobId];
      executionContext.interrupt()
        .catch(e => {
          logger.error(`❌ Failed to interrupt job(jobId=${jobId}), error: ${e}`);
        });
    }, ms > max ? max : ms);
  }

  private _setSimProcessInfoReportInterval(jobId: string) {
    const intervalTime = 60 * 1000;
    const executionContext = jobRunnerCache[jobId];
    const processFile = executionContext.simulationPath.getSimProcessFile();
    return setInterval(() => {
      if (!fs.existsSync(processFile)) {
        // logger.debug(`processFile(${processFile}) not existed`);
        return;
      }
      try {
        const simProcess = JSON.parse(fs.readFileSync(processFile).toString()) as SimProcess;
        this.simProcessReport(jobId, simProcess);
      } catch (e) {
        logger.error(`read processFile(${processFile} error: ${e})`);
      }
    }, intervalTime);
  }

  getJobStatus(jobId: string): JobStatus {
    return this.jobRepo.getJobStatus(jobId);
  }

  findNotFinishedJobs() {
    return this.jobRepo.find(job => {
      return job.status !== JobStatus.Finished;
    });
  }

  jobExist(jobId: string) {
    return this.jobRepo.jobExist(jobId);
  }

  /**
   * 
   * @param simJobDto
   */
  async run(simJobDto: SimulationRequest) {
    let timer: ReturnType<typeof setTimeout>;
    // let simProcessReportInterval: ReturnType<typeof setInterval>;
    try {
      const job = this._buildSimulationJob(simJobDto);
      this.jobRepo.add(job);

      const jobRunner: SimulationJobRunner = buildJobRunner(job);
      jobRunnerCache[job.id] = jobRunner;

      const timeoutMinutes = job.definition.config?.timeout;

      if (timeoutMinutes) {
        timer = this._setTimeoutInterrupt(job.id, timeoutMinutes);
      }
      // simProcessReportInterval = this._setSimProcessInfoReportInterval(job.id);
      
      await jobRunner.execute();
    } catch (e) {
      if (this.jobRepo.jobExist(simJobDto.id)) {
        logger.info(`update simulation job(jobId=${simJobDto.id}) status to failed`);
        this.jobRepo.updateBySelective(simJobDto.id, { status: JobStatus.Finished });
      }
      throw e;
    } finally {
      if (timer) {
        clearTimeout(timer);
      }
      // clearInterval(simProcessReportInterval);
    }
  }

  /**
   * 
   * @param jobId 
   */
  async stop(jobId: string) {
    const jobRunner = jobRunnerCache[jobId];

    if (jobRunner) {
      await jobRunner.interrupt();
    } else {
      logger.warn(`can not find jobRunner(jobId=${jobId})`);
    }
  }

  /**
   * 运行任务队列（FIFO Queue）中队首的 job
   * @param product 仿真产品名称
   */
  async runFromQueue(product: string) {
    const simQueueRepo = SimulationQueueManager.Instance;

    const jobQueue = simQueueRepo.getByName(product);

    if (!jobQueue) { return; }

    let queuedJobId: string;

    try {
      queuedJobId = jobQueue.getNowait() as string;
    } catch (e) {
      if (e.name === QueueExceptionEnum.QUEUE_EMPTY) {
        logger.debug('🙃 There are no waiting jobs in the FIFO queue.');
        return;
      }
      throw e;
    }

    logger.debug(`🧡 Pop a job from FIFO queue, jobId = ${queuedJobId}`);

    const jobRunner = jobRunnerCache[queuedJobId];

    const jobState = jobRunner.getCurrentStateName();
    const jobStatus = this.getJobStatus(queuedJobId);

    if (jobState === 'QueuingState' && jobStatus === JobStatus.Queuing) {
      jobRunner.transitionTo(new RunningState());
      try {
        await jobRunner.execute();
      } catch (e) {
        logger.debug(`#### run from queue(jobId=${queuedJobId}) error: ${e.message}.`);
        const errorResponseBody = {
          id: queuedJobId,
          success: false,
          message: e.message
        } as JobResultNotification;
        await reportService.jobResultReport(errorResponseBody);
      }
    } else {
      logger.warn(`🚘 An incorrect job(JobId=${queuedJobId}) in the queue, jobState: ${jobState}. jobStatus: ${jobStatus}`);
    }
  }

  async simProcessReport(jobId: string, simProcess: SimProcess) {
    try {
      await reportService.jobProcessReport(jobId, simProcess);
    } catch (e) {
      logger.warn(`report sim process(jobId = ${jobId}) error: ${e}`);
    }
  }
}

export const simulationJobService = new SimulationJobService();
